package com.lqian.p1_concurrent_origin_3;

/**
 *  --> 由于 IO 太慢，早期的操作系统就发明了多进程，
 *  即便在单核的 CPU 上我们也可以一边听着歌，一边写 Bug，这个就是多进程的功劳。
 *  操作系统允许某个进程执行一小段时间
 *  例如 50 毫秒，过了 50 毫秒操作系统就会重新选择一个进程来执行（我们称为“任务切换”），这个 50 毫秒称为“时间片”。
 *
 *  --> 在一个时间片内，如果一个进程进行一个IO操作，例如读个文件，这个时候该进程可以把自己标记为"休眠状态"，并出让cpu的使用权
 *      待文件读进内存，操作系统会把这个休眠的进程唤醒，唤醒后的进程就有机会重新获得cpu的使用权了
 *
 *      - 这里的进程在等待 IO 时之所以会释放 CPU 使用权，是为了让 CPU 在这段等待时间里可以做别的事情，
 *      - 这样一来 CPU 的使用率就上来了；
 *      - 此外，如果这时有另外一个进程也读文件，读文件的操作就会排队
 *      - 磁盘驱动在完成一个进程的读操作后，发现有排队的任务，就会立即启动下一个读操作，这样 IO 的使用率也上来了。
 *  --> 虽然看似简单，支持多进程分时复用在操作系统的发展史上却具有里程碑意义，Unix 就是因为解决了这个问题而名噪天下的。
 *
 *  --> 早期的操作系统基于进程来调度CPU，不同进程间是不共享内存空间的，所以进程要做任务切换就必须要同时切换内存映射地址
 *      而，一个进程创建的所有线程，都是共享一个内存空间的
 *      所以，线程做任务切换成本就很低了。现代的操作系统都是基于更轻量级的线程来调度
 *      现在我们提到的"任务切换"都是指"线程切换"
 *
 *  --> Java并发程序都是基于多线程的，自然也会涉及到任务切换，也许你想不到，任务切换竟然也是并发编程里诡异Bug的源头之一。
 *      任务切换的时机大多数是在时间片结束的时候，我们现在基本都使用高级语言编程，高级语言里一条语句往往需要多条cpu指令完成
 *      例如上面代码中的count += 1，至少需要三条cpu指令：
 *          - 指令1：首先，需要把变量count从内存加载到cpu的寄存器
 *          - 指令2：之后，在寄存器执行 + 1操作
 *          - 指令3：最后，将结果写入内存(缓存机制导致可能吸入的cpu缓存而不是内存)
 *  --> 我们潜意识里面觉得 count+=1 这个操作是一个不可分割的整体，就像一个原子一样，线程的切换可以发生在 count+=1 之前，也可以
 *      发生在 count+=1 之后，但就是不会发生在中间。我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。CPU 能
 *      保证的原子操作是 CPU 指令级别的，而不是高级语言的操作符，这是违背我们直觉的地方。
 *      因此，很多时候我们需要在高级语言层面保证操作的原子性。
 * /

    public static int count = 0;
//    private static AtomicInteger atomicInteger = new AtomicInteger(0);
    //可保证原子性（同sync）
    public static void incre(){
        try {
            Thread.sleep(1000); //睡眠1s，其他线程也可能来竞争count
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
            count++;//（同一时间只会有一个线程执行，不存在指令的拆分，也不存在线程的切换的问题）
//        atomicInteger.incrementAndGet();
        /**
         * getstatic 1
         * iadd 2
         * putstatic 3
         */
//    }
public class AtomicDemo {
    public static int count = 0;

    public static void incre() {
        try {
            count ++;
            Thread.sleep(1000); //睡眠1s，其他线程也可能来竞争count
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i <1000 ; i++) {
            new Thread(AtomicDemo::incre).start();
        }
        Thread.sleep(4000); // why sleep here？ 因为线程是异步的，主线程如果执行完了 它不会等待子线程结束
        System.out.println("result:"+count); // must be <= 1000

        //count++ 不满足原子性
        //必须是整个方法是加锁才行
        // >>>>>>>>>>>>>
                        //        synchronized (AotumicDemo.class) {
                        //            count++;
                        //        }

    }
}
